/*
 * AGENT SHERPA
 *  
 * Assignement 2 of the Agent Oriented Software Engineering Course
 * M.Sc. in Advanced Software Engineering (Part-time)
 * University College Dublin www.ucd.ie
 *
 * Copyright (c) 2007 Chris Walsh, Aidan Morrisy and Iain Hull.
 * All rights reserved.
 */

package org.agentsherpa;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.agentsherpa.model.CellType;
import org.agentsherpa.model.Direction;
import org.agentsherpa.model.Drone;
import org.agentsherpa.model.Ied;
import org.agentsherpa.model.Point;

import com.agentfactory.platform.interfaces.Module;

/**
 * LocationModule stores the agents location and map state. Interfaces with the
 * SimulatorService to update the current map.
 */
public class LocationModule extends Module {
    public void init() {
    }

    public void destroy() {
    }
    
    public Map<Point, CellType> sense() {
    	++stepCount;
    	
        try {
            SimulatorService simulator = getSimulator();
            Set<Point> newRoad = simulator.senseRoad(this.agent);
            
            for (Point p : newRoad) {
                if (! road.containsKey(p)) {
                    updateRoad(p, CellType.ROAD);
                }
            }
            
            updateClearedRoad(simulator.getLocation(this.agent));
            updateSensedIeds(simulator.sensedIeds(this.agent));
            updateDroneLocations(simulator.senseDrones(this.agent));
        }
        catch (PerceptorException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return compoundMap;
    }

	private void updateRoad(Point p, CellType cellType) {
		if (road.get(p) != cellType) {
            newRoadMap.put(p, cellType);
		}
        if (! sentRoadSet.contains(p)) {
            newRoadMap.put(p, cellType);
        }
		road.put(p, cellType);
	}
    
    /**
     * Return the currently sensed drones.
     * @return
     */
    public Map<Point, Drone> sensedDrones() {
    	return Collections.unmodifiableMap(sensedDronesByLocation);
    }
    
    /**
     * Return true if the drone should send a map update to the control agent.
     * 
     * @return
     */
    public boolean shouldSendMapUpdate() {
    	return stepCount % STEP_COUNT_FOR_UPDATE == 0;
    }
    
    /**
     * Return the encoded map update.  This is just the newly discovered 
     * parts of the map.
     * 
     * @return
     */
    public String mapDataUpdate() {
    	String ret = MapUtil.encodeMap(newRoadMap);
    	sentRoadSet.addAll(newRoadMap.keySet());
    	newRoadMap.clear();
    	return ret;
    }
    
    
    /**
     * Return the size of map data yet to be sent.
     * @return
     */
	public int mapDataToBeSent() {
		return newRoadMap.size();
	}
	
	
	public void mapMerge(String data) {
		MapUtil.mergeMaps(road, MapUtil.decodeMap(data));
	}


	private void updateDroneLocations(Set<Drone> sensedDrones) {
		sensedDronesByLocation.clear();
		for (Drone d : sensedDrones) {
			sensedDronesByLocation.put(d.getLocation(), d);
		}
	}

	private void updateSensedIeds(Set<Ied> ieds) {
		for (Ied ied : ieds) {
		    if (ied.getStatus() == Ied.Status.Active) {
		    	updateRoad(ied.getLocation(), CellType.IED);
		    }
		}
	}

	private void updateClearedRoad(Point location) {
		updateRoad(location, CellType.VISITED);
		for(Direction dir : Direction.values()) {
			Point p = dir.move(location);
			if (road.get(p) == CellType.ROAD) {
				updateRoad(p, CellType.CLEARED);
			}
		}
	}
    
    private SimulatorService getSimulator() throws PerceptorException {
        SimulatorService simulator = (SimulatorService) agent.getService("org.agentsherpa.SimulatorService");
        if (simulator == null) {
            throw new PerceptorException("Cannot access SimulatorService");
        }
        return simulator;
    }

    private final Map<Point, CellType> road = new HashMap<Point, CellType>();
    private final Map<Point, Drone> sensedDronesByLocation = new HashMap<Point, Drone>();
    private final Map<Point, CellType> compoundMap = new CompoundMap(road, sensedDronesByLocation);
    private final Set<Point> sentRoadSet = new HashSet<Point>();
    private final Map<Point, CellType> newRoadMap = new HashMap<Point, CellType>();
    
	private static final int STEP_COUNT_FOR_UPDATE = 50;
	private int stepCount = 0;
}
